#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>


#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "
#define STREND '\0'

char* argv[MAX_ARGC];
char pwd[SIZE];
char env[SIZE];
int lastcode = 0;

//下面都和重定向有关
#define NoneRedir -1
#define StdinRedir 0
#define StdoutRedir 1
#define AppendRedir 2

#define IgnSpace(buf, pos) do{while(isspace(buf[pos])) pos++;}while(0)

int redir_type = NoneRedir;
char* filename = NULL;


const char* UserName()
{
    char* username = getenv("USER");
    if(username)    return username;
    else return "None";
}

const char* HostName()
{
    char* hostname = getenv("HOSTNAME");
    if(hostname)    return hostname;
    else return "None";
}

const char* CurrentWorkDir()
{
    char* currentworkdir = getenv("PWD");
    if(currentworkdir) return currentworkdir;
    else return "None";
}

char* Home()
{
    return getenv("HOME");
}

int Interactive (char out[], int size)
{
    //输出提示符并获取用户输入的命令字符串"ls -a -l"
    printf("[%s@%s %s]$", UserName(), HostName(), CurrentWorkDir());
    fgets(out, size, stdin); //将键盘输入的，读取到commandline数组里面
    out[strlen(out)-1] = '\0';  //最少输入一个回车吧，将"ls -a -l\n"将回车置为'\0'
    return strlen(out);  //返回字符串的个数，如果只有一个回车，即为空串，啥也不干
}

void CheckRedir(char in[])
{
    //ls -a -l > log.txt
    //ls -a -l >> log.txt
    //cat < log.txt
    redir_type = NoneRedir; //每次进来初始化，因为全局变量
    filename = NULL;
    int pos = strlen(in) -1; //倒着来
    while(pos>=0)
    {
        if(in[pos] == '>')
        {
            if(in[pos-1] == '>')
            {
                redir_type = AppendRedir;
                in[pos-1] = STREND;
                pos++;
                IgnSpace(in, pos);
                filename = in+pos;
                break;
            }
            else 
            {
                redir_type = StdoutRedir;
                in[pos++] = STREND;
                IgnSpace(in, pos);
                filename = in + pos;
                break;
            }
        }
        else if(in[pos] == '<')
        {
            redir_type = StdinRedir;
            in[pos++] = STREND;
            IgnSpace(in, pos);
            filename = in + pos;
            break;
        }
        else 
        {
            pos--;
        }
    }
}

void Split(char in[])
{
    CheckRedir(in);  //检查是否需要重定向
    int i = 0;
    argv[i++] = strtok(in, SEP); //"ls -a -l"  以空格进行分隔切割
    while(argv[i++] = strtok(NULL, SEP));   //最后一次切割失败返回NULL存到最后一个位置，表达式值为NULL  循环结束
    if(strcmp(argv[0], "ls") == 0)  //如果是ls命令带上颜色
    {
        argv[i-1] = (char*)"--color";
        argv[i] = NULL;
    }
}

int BuildCmd()
{
    int ret = 0;
    //1.检测是否是内建命令 ，是 1， 不是 0
    if(strcmp("cd", argv[0]) == 0)
    {
        //2. 执行
        ret = 1;
        char* target = argv[1];  //cd   xxx or cd    cd 空 默认进入家目录
        if(!target) target = Home();    //为空 就将target改成家目录
        chdir(target); //改变当前工作目录
        char temp[1024]; //由于cd xxx 可能是相对路径，所以PWD环境变量要更改成对应的绝对路径，所以调用getcwd函数，可以同步将PWD环境变量修改成绝对路径，这样pwd指令可以显示出绝对路径
        getcwd(temp, 1024); //获取当前工作目录的绝对路径，并存到temp字符数组里面
        snprintf(pwd, SIZE, "PWD=%s", temp);    //将"pwd=%s"%s用temp替换后整体输出
        //到pwd字符数组里面，再导出pwd为新的环境变量
        putenv(pwd);    //导出环境变量pwd修改
    }
    else if(strcmp("export", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1])
        {
            strcpy(env, argv[1]);   //因为argv【1】指向的会变，所以新导入的环境变量需要保存起来
            putenv(env);
        }
    }
    else if(strcmp("echo", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1] == NULL)
        {
            printf("\n");
        }
        else 
        {
            if(argv[1][0] == '$')
            {
                if(argv[1][1] == '?')
                {
                    printf("%d\n", lastcode);   //echo $?   输出最近进程退出码
                    lastcode = 0;
                }
                else 
                {
                    char* e = getenv(argv[1]+1);    //echo $其它环境变量名
                    if(e)   printf("%s\n", e);
                }
            }
            else 
            {
                printf("%s\n", argv[1]);    //正常输出
            }
        }
    }
    return ret;
}

void Execute()
{
    pid_t id = fork();
    if(id==0)
    {
        umask(0);
        int fd = -1;
        if(redir_type == StdinRedir)
        {
            fd = open(filename, O_RDONLY, 0666);
            dup2(fd, 0);
        }
        else if(redir_type == StdoutRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if(redir_type == AppendRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
            dup2(fd, 1);
        }
        else 
        {
            //do nothing
        }
        //让子进程执行命名
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id)   lastcode = WEXITSTATUS(status);
}

int  main()
{
    while(1)
    {
        char commandline[SIZE];
        //1.打印命令提示符，获取用户输入的命令字符串
        int n = Interactive(commandline, SIZE); 
        if(n==0)    continue;    //返回字符串的个数，如果只有一个回车，即为空串,啥也不干
        //2.对命令行字符串进行切割，放到自定义的argv表里面
        Split(commandline);
        //3.切割完，判断命令是否是内建命令，是内建命令myshell进程自己执行，否则子进程执行
        //n 返回1是内建命令，后面不用执行了，返回0不是内建命令，创建子进程执行
        n = BuildCmd();
        if(n)   continue;
       // 4. 不是内建命令，创建子进程执行这个命令
        Execute();
    }
    return 0;
}
